#include "protocal.h"
#include <sys/types.h>
#include <sys/socket.h>
#include <pthread.h>
#include <netinet/in.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <iostream>
#include <string>
#include <thread>
#include <signal.h>

using namespace std;
#define UPDATE_CLIENT_INTERVAL_SEC 		60

int server_socket = 0;
int thread_exit_flag = 0;

pthread_mutex_t mutex;
pthread_t pth_update;
pthread_attr_t pth_update_attr;
UserList ClientList;

void cleanup(int sig);
UserListNode GetUser(char *userName);
void *thread_cliets_update(void *arg);

int main(int argc, char **argv)
{
	int ret = 0;
	int bFound = 0;
	sockaddr_in sender;
	char buf[MAX_PACKET_SIZE] = {0};
	
	signal(SIGINT, cleanup);
	signal(SIGTERM, cleanup);
	signal(SIGKILL, cleanup);
		
	server_socket = socket(AF_INET, SOCK_DGRAM, 0);
	if (server_socket < 0)
	{
		std::cout << "socket init failed\n" << std::endl;
		return 0;
	}

	sockaddr_in addr;
	addr.sin_family = AF_INET;
	addr.sin_port = htons(SERVER_PORT);
	addr.sin_addr.s_addr = htonl(INADDR_ANY);
	if (bind(server_socket, (struct sockaddr*)&addr, sizeof(addr)) < 0)
	{
		std::cout << "bind socket failed!" << std::endl;
		return 0;
	}
	
	pthread_mutex_init(&mutex, NULL);
	ret = pthread_attr_init(&pth_update_attr);
    	if (ret != 0) {
        	perror("pth_update_attr init failed!");
        	return -1;
    	} else {
    		pthread_attr_setdetachstate(&pth_update_attr, PTHREAD_CREATE_DETACHED);
    	}	
    	
    	ret = pthread_create(&pth_update, &pth_update_attr, thread_cliets_update, NULL);
    	if (ret != 0) {
        	perror("thread_cliets_update create failed!");
        	return 0;
    	}

	for (;;) {
		CommMsg* mRecvMsg = (CommMsg*)buf;
		socklen_t nLen = sizeof (sender);
		int ret = recvfrom(server_socket, (char*)mRecvMsg , MAX_PACKET_SIZE, 0, (sockaddr*)&sender, &nLen);
		if (ret < 0) {
			std::cout << "recv error " << std::endl;
			continue;
		} else {
			unsigned int mMsgType = mRecvMsg->uiMsgType;
			switch (mMsgType)
			{
				case LOGIN:
				{	
					UserListNode* currentUser = new UserListNode;
					memcpy(currentUser->userName, mRecvMsg->cMyName, MAX_NAME_SIZE);
					currentUser->uiIP = sender.sin_addr.s_addr;
					currentUser->usPORT = sender.sin_port;
					currentUser->alive_flag = 1;
						
					bFound = 0;
					pthread_mutex_lock(&mutex);
					for(auto UserIterator = ClientList.begin(); UserIterator != ClientList.end(); ++UserIterator) {
						if( strcmp(((*UserIterator)->userName), mRecvMsg->cMyName) == 0) {
							(*UserIterator)->uiIP = sender.sin_addr.s_addr;
							(*UserIterator)->usPORT = sender.sin_port;
							bFound = 1;
							break;
						}
						
						if( ((*UserIterator)->usPORT == currentUser->usPORT) && ((*UserIterator)->uiIP == currentUser->uiIP)) {
							bFound = 1;
							break;
						}						
					}
					pthread_mutex_unlock(&mutex);
						
					if ( !bFound ) {
						std::cout << "Client login: " << currentUser->userName<<"-"<<inet_ntoa(sender.sin_addr)<<":"<< ntohs(currentUser->usPORT)<<std::endl;
						pthread_mutex_lock(&mutex);	
						ClientList.push_back(currentUser);
						pthread_mutex_unlock(&mutex);
					} else {
						std::cout <<"the user name has login" << endl;
					}					
						

					char sendBuf[MAX_PACKET_SIZE] = {0};
					CommMsg* sendbuf = (CommMsg*) sendBuf;
					sendbuf->uiMsgType = GETALLUSER;
					pthread_mutex_lock(&mutex);
					for (auto ClientList_iter = ClientList.begin(); ClientList_iter != ClientList.end(); ++ClientList_iter) {
						memcpy(sendbuf->userList[sendbuf->userNums].userName, (*ClientList_iter)->userName, MAX_NAME_SIZE);
						sendbuf->userList[sendbuf->userNums].uiIP = (*ClientList_iter)->uiIP;
						sendbuf->userList[sendbuf->userNums].usPORT = (*ClientList_iter)->usPORT;
						++sendbuf->userNums;
					}
					pthread_mutex_unlock(&mutex);
					
					sendto(server_socket, (const char*)sendbuf, sendbuf->getSize(), 0, 
							(const sockaddr*)&sender, sizeof(sender));
					break;
				}	
				case LOGOUT:
				{
					std::cout << "Client logout:" << mRecvMsg->cMyName << std::endl;
					pthread_mutex_lock(&mutex);
					for (auto ClientList_iter = ClientList.begin(); ClientList_iter != ClientList.end(); ++ClientList_iter) {
						if (strcmp((*ClientList_iter)->userName, mRecvMsg->cMyName) == 0) {
							ClientList_iter = ClientList.erase(ClientList_iter);
						}
					}
					pthread_mutex_unlock(&mutex);
					
					break;
				}	
				case GETALLUSER:
				{	
					char sendBuf[MAX_PACKET_SIZE] = {0};
					CommMsg* sendbuf = (CommMsg*) sendBuf;
					sendbuf->uiMsgType = GETALLUSER;
					
					pthread_mutex_lock(&mutex);
					for (auto ClientList_iter = ClientList.begin(); ClientList_iter != ClientList.end(); ++ClientList_iter) {
						memcpy(sendbuf->userList[sendbuf->userNums].userName, (*ClientList_iter)->userName,MAX_NAME_SIZE);
						sendbuf->userList[sendbuf->userNums].uiIP = (*ClientList_iter)->uiIP;
						sendbuf->userList[sendbuf->userNums].usPORT = (*ClientList_iter)->usPORT;
						++sendbuf->userNums;
					}
					pthread_mutex_unlock(&mutex);
					
					sendto(server_socket, (const char*)sendbuf, sendbuf->getSize(),0, 
							(const sockaddr*)&sender, sizeof(sender));

					break;
				}	
//				case P2PTRANS:
//				{
//					std::cout << "Init P2P:"<<mRecvMsg->cMyName << "<---->" << mRecvMsg->cToName << std::endl;	
//					UserListNode user = GetUser(mRecvMsg->cToName);
//
//					sockaddr_in remote;
//					remote.sin_family = AF_INET;
//					remote.sin_port = user.usPORT;
//					remote.sin_addr.s_addr = user.uiIP;
//
//					std::cout <<"--> "<< mRecvMsg->cToName <<", IP:"<<inet_ntoa(remote.sin_addr)<<", port:"<<ntohs(user.usPORT)<<endl;
//
//					CommMsg mTransMsg;
//					mTransMsg.uiMsgType = P2P_PUNCHING_REQUEST;
//					mTransMsg.transMsg.uiIP = sender.sin_addr.s_addr; 
//					mTransMsg.transMsg.usPORT = sender.sin_port;
//
//					sendto(server_socket, (const char*)&mTransMsg, sizeof(CommMsg), 0, (const sockaddr*)&remote, sizeof(remote));
//					break;
//				}	
				case KEEPALIVE:
				{	
					UserListNode* currentUser = new UserListNode;
					memcpy(currentUser->userName, mRecvMsg->cMyName, MAX_NAME_SIZE);
					currentUser->uiIP = sender.sin_addr.s_addr;
					currentUser->usPORT = sender.sin_port;
					currentUser->alive_flag = 1;
					
					//std::cout<<"Server recv "<<currentUser->userName<<" keep alive single"<<endl;
					std::cout << "keep alive single from: " << currentUser->userName<<"-"<<inet_ntoa(sender.sin_addr)<<":"<< ntohs(currentUser->usPORT)<<std::endl;
						
					bFound = 0;
					pthread_mutex_lock(&mutex);
					for(auto UserIterator = ClientList.begin(); UserIterator != ClientList.end(); ++UserIterator) {
						if( strcmp( ((*UserIterator)->userName), mRecvMsg->cMyName) == 0 ) {
							bFound = 1;
							(*UserIterator)->alive_flag = 1;
							break;
						}
					}
					pthread_mutex_unlock(&mutex);
						
					if ( !bFound ) {
						std::cout << "Client login: " << currentUser->userName<<"-"<<inet_ntoa(sender.sin_addr)<<":"<< ntohs(currentUser->usPORT)<<std::endl;
						pthread_mutex_lock(&mutex);
						ClientList.push_back(currentUser);
						pthread_mutex_unlock(&mutex);
					}		
								
					break;
				}
			}
			
		}
	
	}

	return 0;
}

UserListNode GetUser(char *userName)
{
	for (auto ClientList_iter = ClientList.begin(); ClientList_iter != ClientList.end(); ++ClientList_iter)
	{
		if (strcmp((*ClientList_iter)->userName, userName) == 0)
			return *(*ClientList_iter);
	}
	
	std::cout << "can not find user: " << userName << std::endl;

	exit(0);
}

void *thread_cliets_update(void *arg) 
{
	
	for(;;) {
		sleep(UPDATE_CLIENT_INTERVAL_SEC);
		
		if (thread_exit_flag)
			break;
		
		pthread_mutex_lock(&mutex);
		for (auto ClientList_iter = ClientList.begin(); ClientList_iter != ClientList.end(); ++ClientList_iter) {
			
			printf("%s->alive_flag = %d\n", (*ClientList_iter)->userName, (*ClientList_iter)->alive_flag);
			if ((*ClientList_iter)->alive_flag == 0) {
				std::cout << (*ClientList_iter)->userName << " did not upate alive flag in "<< UPDATE_CLIENT_INTERVAL_SEC<<"s,and erase it from list"<<endl;
				ClientList_iter = ClientList.erase(ClientList_iter);
			} else
				(*ClientList_iter)->alive_flag = 0;
		}	
		pthread_mutex_unlock(&mutex);
	}
	
}

void thread_exit()
{	
	thread_exit_flag = 1;
    	pthread_attr_destroy(&pth_update_attr);
}

void cleanup(int sig)
{
	std::cout<<__func__<< "invoked! sig ="<<sig<<endl;
	
	if(sig == SIGINT || sig == SIGTERM || sig == SIGKILL) {
		thread_exit();
	}
	
	exit(0);
}